package min3d.sampleProject1;
import java.util.LinkedList;
import min3d.Shared;
import min3d.core.Object3dContainer;
import min3d.core.RendererActivity;
import min3d.core.Scene;
import min3d.objectPrimitives.Box;
import min3d.objectPrimitives.Sphere;
import min3d.parser.IParser;
import min3d.parser.Parser;
import min3d.vos.Color4;
import min3d.vos.Light;
import android.opengl.GLSurfaceView;
import android.os.Bundle;
import android.os.Handler;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;

public class MyActivity extends RendererActivity{
	public Object3dContainer objModel;
	public MyMenu myMenu = new MyMenu(this);
	public MyRenderer r;
	public int vertices, faces;
	public float fps;
	public static String trackerLocation = null;
	public float posX = 0, posY = 0, posZ = 0;
	public float headX = 0, headY = 0, headZ = 0;
	protected int spheresThreshold = 250;
	protected LinkedList<Sphere> spheres = new LinkedList<Sphere>();
	public boolean touchDown = false;
	
	/**
	 * Hook in Android OpenGL.
	 * @param  Bundle  	The state object of the instance.
	 * @return void
	 */
	@Override
    protected void onCreate(Bundle savedInstanceState){
		// from Activity
        super.onCreate(savedInstanceState);
        _initSceneHander = new Handler();
        _updateSceneHander = new Handler();
        // custom renderer
        Shared.context(this);
        scene = new Scene(this);
        r = new MyRenderer(this);
        Shared.renderer(r);
        // from Activity
        _glSurfaceView = new GLSurfaceView(this);
        glSurfaceViewConfig();
        _glSurfaceView.setRenderer(r);
        ViewListener viewListener = new ViewListener(this);
        _glSurfaceView.setOnTouchListener(viewListener);
        _glSurfaceView.setRenderMode(GLSurfaceView.RENDERMODE_CONTINUOUSLY);
        onCreateSetContentView();
    }
	
	/**
	 * Scale the model uniformly.
	 * To be used in subclasses.
	 * @param  float  	Float indicating the scale in all axes.
	 * @return void
	 */
	public void modelScale(float f){
		objModel.scale().x = f;
		objModel.scale().y = f;
		objModel.scale().z = f;
	}
	
	/**
	 * Scale the model non-uniformly.
	 * To be used in subclasses.
	 * @param  float  	Float indicating the scale in the x-axis.
	 * @param  float  	Float indicating the scale in the y-axis.
	 * @param  float  	Float indicating the scale in the z-axis.
	 * @return void
	 */
	public void modelScale(float x, float y, float z){
		objModel.scale().x = x;
		objModel.scale().y = y;
		objModel.scale().z = z;
	}
	
	/**
	 * Position the model.
	 * To be used in subclasses.
	 * @param  float  	Float indicating the position on the x-axis.
	 * @param  float  	Float indicating the position on the y-axis.
	 * @param  float  	Float indicating the position on the z-axis.
	 * @return void
	 */
	public void modelPosition(float x, float y, float z){
		objModel.scale().x = x;
		objModel.scale().y = y;
		objModel.scale().z = z;
	}

	/**
	 * Rotate the model.
	 * To be used in subclasses.
	 * @param  float  	Float indicating the rotation on the x-axis.
	 * @param  float  	Float indicating the rotation on the y-axis.
	 * @param  float  	Float indicating the rotation on the z-axis.
	 * @return void
	 */
	public void modelRotation(float x, float y, float z){
		objModel.rotation().x = x; 
		objModel.rotation().y = y; 
		objModel.rotation().z = z;
	}
	
	/**
	 * Initialize lights and add them to the scene.
	 * 100% ambient, diffuse and specular at default position.
	 * @return void
	 */
	private void lights(){
		scene.lightingEnabled(true);
		Light light = new Light();
	    light.ambient.setAll((short)255, (short)255, (short)255, (short)255);
	    light.diffuse.setAll((short)255, (short)255, (short)255, (short)255);
	    light.specular.setAll((short)255, (short)255, (short)255, (short)255);
	    scene.lights().add(light);
	}
	
	/**
	 * Parses model and adds it to the scene. 
	 * Sets default position and rotation - all 0.
	 * Calculates the number of vertices and faces.
	 * @param  String	The name of the model in the raw folder.
	 * @return void
	 */
	private void setupModel(String modelName){
		// parse model
		IParser parser = Parser.createParser(Parser.Type.OBJ,
				getResources(), "min3d.sampleProject1:raw/" + modelName, true);
		parser.parse();
		objModel = parser.getParsedObject();
		// setup model
		objModel.position().x = 0; objModel.position().y = 0; objModel.position().z = 0;
		objModel.rotation().x = 0; objModel.rotation().y = 0; objModel.rotation().z = 0;
		objModel.colorMaterialEnabled(true);
		objModel.lightingEnabled(true);
		// get vertices and faces
		faces = 0;
		vertices = 0;
		for(int i = 0; i < objModel.numChildren(); i++){
			faces += objModel.getChildAt(0).faces().size();
			vertices += objModel.getChildAt(0).vertices().size();			
		}
		// add object to scene
		scene.addChild(objModel);
	}
	
	/**
	 * Set the position and frustum of the camera.
	 * @return void
	 */
	private void setupCamera(){
		// setup camera
		scene.camera().position.x = 0;
		scene.camera().position.y = 300;
		scene.camera().position.z = 1000;
		scene.camera().frustum.zFar(5000);
		// hack lighting to white by adding invisible sphere
		scene.addChild(new Sphere(0, 1, 1));
	}
	
	/**
	 * Setup a simple box around the model and add it to the scene.
	 * @return void
	 */
	@SuppressWarnings("unused")
	private void setupRoom(){
		int size = 4000;
		Color4 col = new Color4(100, 100, 100, 100);
		Box room = new Box(size, size, size, col);
		room.doubleSidedEnabled(true);
		room.position().y = 2000;
		room.position().z = 1000;
		scene.addChild(room);
	}
	
	/**
	 * Must be called in the first line of initScene() in subclasses.
	 * @param  String  	The name of the model in the raw folder.
	 * @return void
	 */
	public void setupScene(String modelName){
		Log.v("SETUP", "***\n***\nStarting scene setup.\n***\n***");
		lights();
		setupModel(modelName);
		//setupRoom();
		r.logFps(true);
		setupCamera();
		Log.v("SETUP", "***\n***\nFinished scene setup.\n***\n***");
	}
		
	/**
	 * Updates headX, headY, headZ according to the tracker information.
	 * If myMenu.interactive is true, tracker 2 is used for the second phone tracker
	 * else, it is used as the head tracker for the first phone.
	 * And update the camera accordingly and model.
	 * @return void
	 */
	private void updateHead(){
		int i = 0, big = 1000;
		float[] coords = new float[16];
		try{
			String[] split = trackerLocation.split("\\|")[1].split("\\,");
			for(i = 0; i < 16; i++)
				coords[i] = Float.parseFloat(split[i]);
			headX = coords[12];
			headY = coords[14];
			headZ = coords[13]*-1;
			
			if(myMenu.interactive && myMenu.tracker2){
				scene.camera().position.x = headX;
				scene.camera().position.y = headY;
				scene.camera().position.z = headZ;
				
				// set target 	(POINT)
				scene.camera().target.x = big*coords[4];
				scene.camera().target.y = big*coords[6];
				scene.camera().target.z = big*coords[5]*-1;
				
				// set up 		(VECTOR)
				scene.camera().upAxis.x = coords[8];
				scene.camera().upAxis.y = coords[10];
				scene.camera().upAxis.z = coords[9]*-1;
			}
		} catch(Exception e){}
		if(myMenu.interactive){
			return;
		}

		try{
			// split the string and parse to float array
			String[] split = trackerLocation.split("\\|")[1].split("\\,");
			for(i = 0; i < 16; i++)
				coords[i] = Float.parseFloat(split[i]);
			float x = coords[12];
			float y = coords[14];
			float z = coords[13]*-1;
			float offX = z*(posX-x)/(z-posZ)*-1;
			float offY = z*(posY-y)/(z-posZ);
			float offZ = z-posZ;

			objModel.position().x = offX; 
			objModel.position().y = offY;
			objModel.position().z = -offZ;
		} catch (Exception e){
			Log.v("HEAD fail", "i="+i+", "+coords[i]);
		}	
	}

	/**
	 * Updates posX, posY, posZ according to the tracker information and
	 * update the camera accordingly.
	 * @return void
	 */
	private void updateCamera(){
		int i = 0, big = 1000;
		float[] coords = new float[16];
		try{
			// split the string and parse to float array
			String[] split = trackerLocation.split("\\,");
			for(i = 0; i < 16; i++)
				coords[i] = Float.parseFloat(split[i]);

			posX = coords[12];
			posY = coords[14];
			posZ = coords[13]*-1;
			// set position (POINT)
			scene.camera().position.x = posX;
			scene.camera().position.y = posY;
			scene.camera().position.z = posZ;
			
			// set target 	(POINT)
			scene.camera().target.x = big*coords[4];
			scene.camera().target.y = big*coords[6];
			scene.camera().target.z = big*coords[5]*-1;
			
			// set up 		(VECTOR)
			scene.camera().upAxis.x = coords[8];
			scene.camera().upAxis.y = coords[10];
			scene.camera().upAxis.z = coords[9]*-1;

		} catch (Exception e){
			Log.v("CAM fail", "i="+i+", "+coords[i]);
		}
	}
	
	/**
	 * If tracking, updates the camera and head.
	 * Checks the visibility of the model.
	 * Update the camera accordingly and enable fps logging.
	 * @return void
	 */
	@Override
	public void updateScene(){
		// check tracking
		if(myMenu.trackingOn && trackerLocation != null){
			updateCamera();
			updateHead();
		}
		// check object visibility
		objModel.isVisible(myMenu.showModel);
		// update fps
		fps = r.fps();
	}
	
	/**
	 * Pass on the action of creating the menu to the myMenu object.
	 * @return boolean 	true if the action was handled, false otherwise.
	 */
    @Override
    public boolean onCreateOptionsMenu(Menu menu){
    	return myMenu.init(menu);
    }
    
    /**
	 * Pass on the action of selecting and item to the myMenu object.
	 * @return boolean 	true if the action was handled, false otherwise.
	 */
    @Override
    public boolean onOptionsItemSelected(MenuItem item){
    	return myMenu.select(item);
    }
}
